package net.linku.annotation.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import net.linku.annotation.ChangeRecordEnums;
import net.linku.annotation.ChangeRecordLog;
import net.linku.annotation.change.service.ContentParser;
import net.linku.annotation.config.LinkUExpressionEvaluator;
import net.linku.annotation.vo.ChangeRecordBizVO;
import net.linku.service.impl.LAAccountServiceImpl;
import net.linku.utils.JsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据变更记录切面，用于记录数据变更日志，包括插入、删除和修改操作。
 *
 * <p>该切面通过拦截标记了 {@link ChangeRecordLog} 注解的方法，实现对数据变更的日志记录功能。</p>
 *
 * <p>主要功能包括：</p>
 * <ul>
 *     <li>记录变更操作前后的数据</li>
 *     <li>根据注解配置决定是否进行比较操作</li>
 *     <li>保存变更记录到数据库</li>
 * </ul>
 *
 */
@Aspect
@Component
@Slf4j
@SuppressWarnings("all")
public class ChangeRecordAspect implements ApplicationContextAware {

    /**
     * 表达式处理工具
     */
    private static final LinkUExpressionEvaluator EVALUATOR = new LinkUExpressionEvaluator();

    @Resource
    ContentParser defaultContentParse;

    @Resource
    LAAccountServiceImpl changeRecordBizService;

    private ApplicationContext applicationContext;

    /**
     * 环绕通知，拦截标记了 {@link ChangeRecordLog} 注解的方法。
     *
     * @param joinPoint 连接点，提供对目标方法的访问
     * @return 目标方法的返回值
     * @throws Throwable 可能抛出的异常
     */
    @Around("@annotation(net.linku.annotation.ChangeRecordLog)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Long startTime = System.currentTimeMillis();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取方法的参数，即变更后的对象
        Object[] newValue = joinPoint.getArgs();

        // 获取注解信息
        ChangeRecordLog changeRecordLog = method.getAnnotation(ChangeRecordLog.class);

        // 判断是否需要比对对象差异
        boolean needCompare = changeRecordLog.needCompare();

        if (needCompare) {
            return needCompareExecute(newValue, joinPoint, changeRecordLog);
        }
        return noNeedCompareExecute(newValue, joinPoint, changeRecordLog);
    }

    /**
     * 插入操作时保存变更记录
     *
     * @param changeRecordLog 注解信息
     * @param idList          变更记录的 ID 列表
     */
    private void insertSaveHistory(ChangeRecordLog changeRecordLog, List<Long> idList) {
        ChangeRecordEnums.ACTION action = changeRecordLog.action();
        if (ChangeRecordEnums.ACTION.INSERT.getAction().equalsIgnoreCase(action.getAction())) {
            changeRecordBizService.saveInsertHistory(idList, changeRecordLog);
        }
    }

    /**
     * 删除操作时保存变更记录
     *
     * @param oldValueObjectMap 变更前的数据映射
     * @param changeRecordLog   注解信息
     * @param idList            变更记录的 ID 列表
     */
    private void deleteSaveHistory(Map<Object, Object> oldValueObjectMap, ChangeRecordLog changeRecordLog, List<Long> idList) {
        ChangeRecordEnums.ACTION action = changeRecordLog.action();
        if (ChangeRecordEnums.ACTION.DELETE.getAction().equalsIgnoreCase(action.getAction())) {
            changeRecordBizService.saveDeleteHistory(oldValueObjectMap, idList, changeRecordLog);
        }
    }

    /**
     * 修改操作时保存变更记录
     *
     * <p>暂时不考虑输出差异内容。</p>
     *
     * @param oldValueObjectMap 变更前的数据映射
     * @param changeRecordLog   注解信息
     * @param idList            变更记录的 ID 列表
     */
    private void modifySaveHistory(Map<Object, Object> oldValueObjectMap, ChangeRecordLog changeRecordLog, List<Long> idList) {
        ChangeRecordEnums.ACTION action = changeRecordLog.action();
        if (ChangeRecordEnums.ACTION.MODIFY.getAction().equalsIgnoreCase(action.getAction())) {
            changeRecordBizService.modifySaveHistory(oldValueObjectMap, idList, changeRecordLog);
        }
    }

    /**
     * 无需比较操作前后数据时的处理逻辑
     *
     * @param newValue        方法参数，即变更后的对象
     * @param joinPoint       连接点
     * @param changeRecordLog 注解信息
     * @return 目标方法的返回值
     * @throws Throwable 可能抛出的异常
     */
    private Object noNeedCompareExecute(Object[] newValue, ProceedingJoinPoint joinPoint, ChangeRecordLog changeRecordLog) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 获取 ID 列表
        List<Long> idList = evalParam(joinPoint, changeRecordLog.param());

        // 执行目标方法，获取返回值
        Object executeResult = joinPoint.proceed();

        stopWatch.stop();
        log.info("noNeedCompareExecute-执行目标方法耗时:{} ms", stopWatch.getTotalTimeMillis());

        stopWatch.start();

        // 生成变更记录并保存
        List<ChangeRecordBizVO> changeRecordList = Optional.ofNullable(idList)
                .orElse(Collections.emptyList())
                .stream()
                .map(id -> ChangeRecordBizVO.builder()
                        .businessId(String.valueOf(id))
                        .businessTypeEnums(changeRecordLog.businessType())
                        .actionEnums(changeRecordLog.action())
                        .changeType(changeRecordLog.changeType())
                        .changeDesc(changeRecordLog.desc())
                        .build())
                .collect(Collectors.toList());

        changeRecordBizService.saveBatchChangeListHistory(changeRecordList);

        stopWatch.stop();
        log.info("noNeedCompareExecute-保存变更记录耗时:{} ms", stopWatch.getTotalTimeMillis());

        return executeResult;
    }

    /**
     * 需要比较操作前后数据时的处理逻辑
     *
     * @param newValue        方法参数，即变更后的对象
     * @param joinPoint       连接点
     * @param changeRecordLog 注解信息
     * @return 目标方法的返回值
     * @throws Throwable 可能抛出的异常
     */
    private Object needCompareExecute(Object[] newValue, ProceedingJoinPoint joinPoint, ChangeRecordLog changeRecordLog) throws Throwable {
        StopWatch stopWatch = new StopWatch();

        Object changeObj = newValue[0];

        // 自动检查操作类型
        autoCheckAction(changeObj, changeRecordLog);

        stopWatch.start();
        // 获取变更前的数据
        Map<Object, Object> oldValueObjectMap = getOldObjectResult(changeObj, joinPoint, changeRecordLog);
        stopWatch.stop();
        log.info("needCompareExecute-获取变更前耗时:{} ms", stopWatch.getTotalTimeMillis());

        stopWatch.start();
        // 执行目标方法，获取返回值
        Object executeResult = joinPoint.proceed();
        stopWatch.stop();
        log.info("needCompareExecute-执行目标方法耗时:{} ms", stopWatch.getTotalTimeMillis());

        try {
            // 记录变更日志
            stopWatch.start();
            if (Objects.nonNull(changeRecordLog)) {
                // 获取变更后的 ID 列表
                List<Long> idList = evalParam(joinPoint, changeRecordLog.param());

                insertSaveHistory(changeRecordLog, idList);
                deleteSaveHistory(oldValueObjectMap, changeRecordLog, idList);
                modifySaveHistory(oldValueObjectMap, changeRecordLog, idList);
            }
            stopWatch.stop();
            log.info("needCompareExecute-写入完变更记录耗时:{} ms", stopWatch.getTotalTimeMillis());
        } catch (Exception ex) {
            log.error("needCompareExecute-写入变更记录异常, oldValue: {}", JsonUtil.toJson(oldValueObjectMap));
        }
        return executeResult;
    }

    /**
     * 设置 Spring 上下文
     *
     * @param applicationContext Spring 应用上下文
     * @throws BeansException 可能抛出的异常
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 自动检查操作类型，并在需要时调整操作类型为插入
     *
     * @param changeObj       变更对象
     * @param changeRecordLog 注解信息
     * @throws NoSuchFieldException   如果没有找到字段
     * @throws IllegalAccessException 如果无法访问字段
     */
    private void autoCheckAction(Object changeObj, ChangeRecordLog changeRecordLog) throws NoSuchFieldException, IllegalAccessException {
        ChangeRecordEnums.ACTION action = changeRecordLog.action();
        if (!action.getAction().equalsIgnoreCase(ChangeRecordEnums.ACTION.INSERT_OR_MODIFY.getAction())) {
            return;
        }

        Object id = ReflectUtil.getFieldValue(changeObj, changeRecordLog.keyId());
        if (Objects.isNull(id)) {
            // 使用具体类型的 Map
            final String actionName = "action";
            InvocationHandler invocationHandler = Proxy.getInvocationHandler(changeRecordLog);
            Field field = invocationHandler.getClass().getDeclaredField("memberValues");
            field.setAccessible(true);
            @SuppressWarnings("unchecked")
            Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
            memberValues.put(actionName, ChangeRecordEnums.ACTION.INSERT);
        }
    }


    /**
     * 解析并获取方法参数中的 ID 列表
     *
     * @param point      连接点
     * @param keyIdParam 表达式，指定获取 ID 的参数
     * @return ID 列表
     */
    private List<Long> evalParam(ProceedingJoinPoint point, String keyIdParam) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        EvaluationContext context = EVALUATOR.createContext(method, args, target, targetClass, applicationContext);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        return EVALUATOR.eval(keyIdParam, elementKey, context, List.class);
    }

    /**
     * 根据 ID 列表从数据库中获取变更前的数据
     *
     * @param changeObj       变更对象
     * @param joinPoint       连接点
     * @param changeRecordLog 注解信息
     * @return 旧值对象的映射
     */
    private Map<Object, Object> getOldObjectResult(Object changeObj, ProceedingJoinPoint joinPoint, ChangeRecordLog changeRecordLog) {
        if (ChangeRecordEnums.ACTION.MODIFY.getAction().equalsIgnoreCase(changeRecordLog.action().getAction())) {
            // 获取变更的 ID 列表
            List<Long> idList = evalParam(joinPoint, changeRecordLog.param());
            if (CollUtil.isEmpty(idList)) {
                return Collections.emptyMap();
            }
            return idList.stream().collect(Collectors.toMap(
                    modifyId -> modifyId,
                    modifyId -> defaultContentParse.getResultById(modifyId, changeRecordLog),
                    (v1, v2) -> v1
            ));
        }
        return Collections.emptyMap();
    }

    /**
     * 判断对象是否为列表类型
     *
     * @param obj 对象
     * @return 如果对象是列表类型，返回 true，否则返回 false
     */
    private boolean isList(Object obj) {
        return (obj instanceof List<?>);
    }
}
